<?php
/**
 * Created by PhpStorm.
 * User: syzhou
 * Date: 2019-08-26
 * Time: 20:04
 */

namespace App\Services;


use App\Project;
use App\ReviewerGroup;
use App\ReviewerGroupProject;
use App\ReviewerGroupReviewer;
use App\ReviewerScoreProject;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class AssignProjectService
{
    /**
     * @var WorkflowService
     */
    private $workflowSvc;

    /**
     * ReviewerGroupService constructor.
     * @throws \Exception
     */
    public function __construct()
    {
        $this->workflowSvc = new WorkflowService();
    }

    /**
     * 分配指定大组下所有项目到该组的专家组
     * @param $largeGroupId
     * @return array 模式：
     * - ok: bool
     * - code: int 错误码
     *     0: 成功
     *     2: 数据库错误
     *     3: 现在不是分配评审阶段，不能分配项目
     *     4: 目前没有专家组，请先创建专家组，再分配项目
     *     5: 有专家不能参加评审，请确认所有专家都能参加评审，再分配项目
     * - msg: string
     */
    public function assignProjects($largeGroupId)
    {
        if ($this->workflowSvc->notAtPhases([WorkflowService::PHASE_ASSIGN_REVIEW], $largeGroupId)) {
            return ['ok' => false, 'code' => 3, 'msg' => '现在不是分配评审阶段，不能分配项目'];
        }

        try {
            // 获取待分配项目列表
            $projects = DB::table('applications as a')
                ->where('large_group_id', $largeGroupId)
                ->whereIn('status_id', [Project::STATUS_ASSIGN_REVIEW_TO_ASSIGN,
                    Project::STATUS_ASSIGN_REVIEW_TO_SUBMIT_ASSIGN])
                ->leftJoin('users as b', 'a.user_id', '=', 'b.id')
                ->leftJoin('application_form_data as c', 'c.id', '=', 'a.id')
                ->select('a.*','c.form_data', 'b.name as applicant_name', 'b.work_org_name as applicant_work_org_name')
                ->get()->toArray();
            if (empty($projects)) {
                return [
                    'ok' => true,
                    'code' => 0,
                    'msg' => '没有可分配的项目',
                ];
            }

            foreach ($projects as $indexProjects => $itemProjects) {
                $form_data = $itemProjects->form_data;
                $itemProjects->form_data = '';
                $itemProjects->complete_companies_unit = '';
                if (empty($form_data)) {
                    continue;
                }
                $form_data = json_decode($form_data, true);
                if (empty($form_data)) {
                    continue;
                }

                if (empty($form_data['basic_situation']['complete_companies'])){
                    continue;
                }
                $itemProjects->complete_companies_unit = $form_data['basic_situation']['complete_companies'];
            }

            // 获取候选专家组列表
            $reviewerGroups = DB::table('reviewer_groups')
                ->where('large_group_id', $largeGroupId)
                ->orderBy('reviewer_group_order')
                ->get()->toArray();
            if (empty($reviewerGroups)) {
                return [
                    'ok' => false,
                    'code' => 4,
                    'msg' => '目前没有专家组，请先创建专家组，再分配项目'
                ];
            }

            // 检查是否所有专家都能参加评审
            $cannotAttendReviewReviewerCount = DB::table('reviewer_group_reviewers')
                ->where('large_group_id', $largeGroupId)
                ->where('can_attend_review', 0)
                ->count();
            if ($cannotAttendReviewReviewerCount > 0) {
                return [
                    'ok' => false,
                    'code' => 5,
                    'msg' => "有 $cannotAttendReviewReviewerCount 个专家不能参加评审，不能分配项目"
                ];
            }

            // 获取专家组的专家
            $reviewerGroupReviewers = DB::table('reviewer_group_reviewers')
                ->where('large_group_id', $largeGroupId)
                ->get()->toArray();
        } catch (\Exception $e) {
            Log::error('[ReviewerGroupService::assignProjects]', ['exception' => $e]);
            return [
                'ok' => false,
                'code' => 2,
                'msg' => '数据库错误'
            ];
        }
        $reviewerGroupId2Reviewers = [];
        foreach ($reviewerGroupReviewers as $reviewerGroupReviewer) {
            $reviewerGroupId = $reviewerGroupReviewer->reviewer_group_id;
            if (!isset($reviewerGroupId2Reviewers[$reviewerGroupId])) {
                $reviewerGroupId2Reviewers[$reviewerGroupId] = [];
            }
            $reviewerGroupId2Reviewers[$reviewerGroupId][] = $reviewerGroupReviewer;
        }

        // 轮流给每个专家组，随机分配一个匹配的项目
        $projectIds = array_map(function ($project) {
            return $project->id;
        }, $projects);
        $reviewer_group_projects_records = [];
        $reviewer_score_projects_records = [];
        $reviewerGroupCount = count($reviewerGroups);
        $reviewerGroupIndex = 0;
        while (!empty($projects)) {
            $reviewerGroup = $reviewerGroups[$reviewerGroupIndex];
            $reviewerGroupId = $reviewerGroup->id;
            $candidateProjects = array_filter($projects, function($project) use ($reviewerGroup) {
                return $this->projectMatchReviewerGroup($project, $reviewerGroup);
            });

            // 若当前专家组无匹配的项目，则从候选专家组列表中剔除该专家组
            if (empty($candidateProjects)) {
                unset($reviewerGroups[$reviewerGroupIndex]);
                $reviewerGroupCount--;
                if ($reviewerGroupCount == 0) {  // 没有候选专家组里，分配失败
                    $remaingingProjectCount = count($projects);
                    return [
                        'ok' => false,
                        'code' => 6,
                        'msg' => "根据专家组的回避关键词，有 $remaingingProjectCount 个项目无法分配",
                    ];
                }
                $reviewerGroups = array_values($reviewerGroups);
                $reviewerGroupIndex = $reviewerGroupIndex % $reviewerGroupCount;
                continue;
            } else {
                $reviewerGroupIndex = ($reviewerGroupIndex + 1) % $reviewerGroupCount;
            }

            $projectKey = array_rand($candidateProjects, 1);
            $project = $candidateProjects[$projectKey];
            unset($projects[$projectKey]);
            $reviewer_group_projects_records[] = [
                'project_id' => $project->id,
                'reviewer_group_id' => $reviewerGroupId,
                'reviewer_group_review_order' => 1,
                'large_group_id' => $largeGroupId,
                'large_group_leader_review_result' => null,
                'avg_score' => null,
                'score_status' => ReviewerGroupProject::SCORE_STATUS_TO_SCORE,
            ];
            foreach ($reviewerGroupId2Reviewers[$reviewerGroupId] as $reviewer) {
                $reviewer_score_projects_records[] = [
                    'project_id' => $project->id,
                    'reviewer_id' => $reviewer->reviewer_id,
                    'reviewer_group_id' => $reviewerGroupId,
                    'reviewer_group_review_order' => 1,
                    'large_group_id' => $largeGroupId,
                    'score' => null,
                    'form_data' => '{}',
                    'is_submitted' => 0,
                ];
            }
        }

        $inTransaction = false;
        try {
            DB::beginTransaction();
            $inTransaction = true;

            // 清除以前的分配结果
            ReviewerScoreProject::where('large_group_id', $largeGroupId)->delete();
            ReviewerGroupProject::where('large_group_id', $largeGroupId)->delete();

            DB::table('reviewer_group_projects')->insert($reviewer_group_projects_records);
            DB::table('reviewer_score_projects')->insert($reviewer_score_projects_records);
            Project::whereIn('id', $projectIds)->update(['status_id' => Project::STATUS_ASSIGN_REVIEW_TO_SUBMIT_ASSIGN]);
            DB::commit();
            return [
                'ok' => true,
                'code' => 0,
                'msg' => 'ok',
            ];
        } catch (\Exception $e) {
            Log::error('[ReviewerGroupService::assignProjects]', ['exception' => $e]);
            if ($inTransaction) {
                try {
                    DB::rollBack();
                } catch (\Exception $e) {
                    Log::error('[ReviewerGroupService::assignProjects]', ['exception' => $e]);
                }
            }
            return [
                'ok' => false,
                'code' => 2,
                'msg' => '数据库错误'
            ];
        }
    }

    /**
     * 某项目是否能被分配给某专家组
     * @param $project
     * @param $reviewerGroup
     * @return bool
     */
    private function projectMatchReviewerGroup($project, $reviewerGroup)
    {
        $reviewerGroupKeywords = $reviewerGroup->keywords;
        $aReviewerGroupKeywords = array_filter(explode(',', $reviewerGroupKeywords));
//        $projectFields = ['applicant_name', 'applicant_work_org_name'];
        $projectFields = ['complete_companies_unit'];
        foreach ($projectFields as $projectField) {
            foreach ($aReviewerGroupKeywords as $keyword) {
                $getText = $project->{$projectField};
                if (strpos($getText, $keyword) !== false) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * @param $largeGroupId
     * @return array 模式：
     * - ok: bool
     * - code: int 错误码
     *     0: 成功
     *     2: 数据库错误
     * - msg: string
     */
    public function cancelAssignedProjects($largeGroupId)
    {
        if ($this->workflowSvc->notAtPhases([WorkflowService::PHASE_ASSIGN_REVIEW], $largeGroupId)) {
            return ['ok' => false, 'code' => 3, 'msg' => '现在不是分配评审阶段，不能取消分配项目'];
        }

        $inTransaction = false;
        try {

            DB::beginTransaction();
            $inTransaction = true;
            ReviewerScoreProject::where('large_group_id', $largeGroupId)->delete();
            ReviewerGroupProject::where('large_group_id', $largeGroupId)->delete();
            Project::where('large_group_id', $largeGroupId)
                ->where('status_id', Project::STATUS_ASSIGN_REVIEW_TO_SUBMIT_ASSIGN)
                ->update(['status_id' => Project::STATUS_ASSIGN_REVIEW_TO_ASSIGN]);
            DB::commit();
            return [
                'ok' => true,
                'code' => 0,
                'msg' => 'ok'
            ];
        } catch (\Exception $e) {
            Log::error('[ReviewerGroupService::cancelAssignProjects]', ['exception' => $e]);
            if ($inTransaction) {
                try {
                    DB::rollBack();
                } catch (\Exception $e) {

                }
            }
            return [
                'ok' => false,
                'code' => 2,
                'msg' => '数据库错误'
            ];
        }
    }

    /**
     * 提交指定大组的已分配项目至专家评审
     * @param $largeGroupId
     * @return array 模式:
     * - ok: bool
     * - code: int 错误码
     *     0: 成功
     *     2: 数据库错误
     *     3: 现在不是分配评审阶段，不能提交分配项目
     *     4: 形式审查阶段未结束，不能提交分配项目
     *     5: 还有项目未分配，不能提交
     *     6: 还有专家不能参加评审，不能提交
     *     7: 结束【分配评审】阶段时出错
     *     8: 开始【专家评审】阶段时出错
     * - msg: string
     */
    public function submitAssignedProjects($largeGroupId)
    {
        $largeGroupId = (int)$largeGroupId;
        if ($this->workflowSvc->notAtPhases([WorkflowService::PHASE_ASSIGN_REVIEW], $largeGroupId)) {
            return [
                'ok' => false,
                'code' => 3,
                'msg' => "现在不是分配评审阶段，不能提交分配项目"
            ];
        }
        if ($this->workflowSvc->atPhases([WorkflowService::PHASE_FORMAL_REVIEW])) {
            return [
                'ok' => false,
                'code' => 4,
                'msg' => "形式审查阶段未结束，不能提交分配项目"
            ];
        }
        $rows = DB::select("select count(*) count from applications where status_id = ? and large_group_id = ?"
            . " and id not in (select project_id from reviewer_group_projects)",
            [Project::STATUS_ASSIGN_REVIEW_TO_ASSIGN, $largeGroupId]);
        $unassignedProjectCount = $rows[0]->count;
        if ($unassignedProjectCount > 0) {
            return [
                'ok' => false,
                'code' => 5,
                'msg' => "还有 $unassignedProjectCount 个项目未分配，不能提交"
            ];
        }

        $cannotAttendReviewReviewerCount = ReviewerGroupReviewer::where('large_group_id', $largeGroupId)
            ->where('can_attend_review', 0)
            ->count();
        if ($cannotAttendReviewReviewerCount > 0) {
            return [
                'ok' => false,
                'code' => 6,
                'msg' => "还有 $cannotAttendReviewReviewerCount 个专家不能参加评审，不能提交"
            ];
        }

        $inTransaction = false;
        try {
            DB::beginTransaction();
            $inTransaction = true;
            Project::where('large_group_id', $largeGroupId)
                ->where('status_id', Project::STATUS_ASSIGN_REVIEW_TO_SUBMIT_ASSIGN)
                ->update(['status_id' => Project::STATUS_EXPERT_REVIEW_TO_SCORE]);
            $ret = $this->workflowSvc->endPhase(WorkflowService::PHASE_ASSIGN_REVIEW, $largeGroupId, $inTransaction);
            if (!$ret['ok']) {
                return [
                    'ok' => false,
                    'code' => 7,
                    'msg' => "结束【分配评审】阶段时出错: {$ret['msg']}"
                ];
            }
            $ret = $this->workflowSvc->beginPhase(WorkflowService::PHASE_EXPERT_REVIEW, $largeGroupId, $inTransaction);
            if (!$ret['ok']) {
                return [
                    'ok' => false,
                    'code' => 8,
                    'msg' => "开始【专家评审】阶段时出错: {$ret['msg']}"
                ];
            }
            DB::commit();
            return [
                'ok' => true,
                'code' => 0,
                'msg' => 'ok'
            ];
        } catch (\Exception $e) {
            Log::error('[ReviewerGroupService::cancelAssignProjects]', ['exception' => $e]);
            if ($inTransaction) {
                try {
                    DB::rollBack();
                } catch (\Exception $e) {

                }
            }
            return [
                'ok' => false,
                'code' => 2,
                'msg' => '数据库错误'
            ];
        }
    }

    /**
     * 获取能安排给指定的项目进行重新评审的专家组列表
     * @param $projectId
     * @return array
     */
    public function reviewerGroupsForNewReview($projectId) {
        try {
            $largeGroupId = Project::where('id', $projectId)->pluck('large_group_id');
            $reviewerGroups = ReviewerGroup::where('large_group_id', $largeGroupId)->get();
        } catch (\Exception $e) {
            Log::error('', ['exception' => $e]);
            return [
                'ok' => false,
                'code' => 2,
                'msg' => '数据库错误',
            ];
        }
    }

}